package org.fhnw.aigs.server.gameHandling;
import org.fhnw.aigs.server.common.ServerConfiguration;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import org.fhnw.aigs.commons.Game;
import org.fhnw.aigs.commons.Player;
import org.fhnw.aigs.commons.communication.KeepAliveMessage;
import org.fhnw.aigs.server.common.LogRouter;
import org.fhnw.aigs.server.common.LoggingLevel;
/**
* This class helps detecting idle clients.<br>
* It runs a <b>KeepAliveMessage</b> in a predefined interval.<br>
* This interval is stored in the attribute <b>keepAliveTimeOut</b> and is
* defined in the ServerConfiguration. It can be changed, when needed. The
* server must be restarted to use the new interval. Every client is obliged to
* send a response to the KeepAliveMessage within that time frame. If it does
* not, the clients will be disconnected and therefore the game will end.
* <b>Please note:</b> Do not use a low keepAliveTimeOut. If the user debugs the
* project, the break point may block the communication between Client and
* Server, therefore the signals cannot reach the KeepAliveManager. The server
* will then interpret this as a time out. An interval of one or two minutes
* (60000 to 120000 ms) is sufficient to detect blocked clients. Is is possible
* to turn of the KeepAliveManager by setting "UseKeepAliveManager" in the
* Server Configuration to "false".<br>
* v1.0 Initial release<br>
* v1.1 Changing of logging
*
* @author Matthias Stöckli (v1.0)
* @version v1.1 (Raphael Stoeckli 24.02.2015)
*/
public class KeepAliveManager implements Runnable {
/**
* A list with all players (identified by their name) that still need to
* send a response to the KeepAliveMessage
*/
private static HashMap<String, Player> duePlayers = new HashMap<String, Player>();
/**
* A list with games that needs to be closed due to a time out
*/
private static ArrayList<Game> gamesToBeClosed = new ArrayList<Game>();
/**
* The interval between a KeepAliveMessage and the expected answer
*/
private int keepAliveTimeOut;
/**
* This method will be called from the ServerMessageBroker in order to
* inform the KeepAliveManager of an incoming response.
*
* @param keepAliveResponse The response of a client in the form of a
* KeepAliveMessage
*/
public static synchronized void handleResponse(KeepAliveMessage keepAliveResponse) {
if (keepAliveResponse.getPlayer() != null) {
String name = keepAliveResponse.getPlayer().getName();
if (duePlayers.containsKey(name)) {
duePlayers.remove(name);
}
}
}
/**
* Starts {@link KeepAliveManager#startKeepAliveLoop}.
*/
@Override
public void run() {
startKeepAliveLoop();
}
/**
* This method starts the KeepAlive loop. It iterates through every active
* game and sends a {@link KeepAliveMessage} to all players (except AI).
* Then the players are put on a list. Now all client's will receive a
* message to which they must provide an answer (a message of the same
* type). It they do so, the players will be removed from the list. After a
* predefined interval, the list will be checked again. Those players who
* did not send an answer will be regarded as inactive. The games they are
* in will then be closed. Then the list is cleared, and so the process
* begins anew.
*/
private synchronized void startKeepAliveLoop() {
keepAliveTimeOut = ServerConfiguration.getInstance().getKeepAliveTimeOut();
while (true) {
// Go through all the running games
for (Game game : GameManager.runningGames) {
for (Player player : game.getPlayers()) {
if (player.isAi()) {
continue;
}
KeepAliveMessage keepAliveMessage = new KeepAliveMessage();
keepAliveMessage.setSentTime(new Date());
game.sendMessageToPlayer(keepAliveMessage, player);
duePlayers.put(player.getName(), player);
//LOG//Logger.getLogger(KeepAliveManager.class.getName()).log(Level.FINE, "Sent KeepAlive to {0}", player.getName());
LogRouter.log(KeepAliveManager.class.getName(), LoggingLevel.info, "Sent KeepAlive to {0}", player.getName());
}
}
// Wait for the amount of time defined in the keepAliveTimeOut
// See ServerConfiguration.xml for that.
try {
Thread.sleep(keepAliveTimeOut);
} catch (InterruptedException ex) {
//LOG//Logger.getLogger(KeepAliveManager.class.getName()).log(Level.SEVERE, null, ex);
LogRouter.log(KeepAliveManager.class.getName(), LoggingLevel.severe, null, ex);
}
// Iterate through all games again and check whether an inactive player
// is in. If this is the case, close the game.
for (Game game : GameManager.runningGames) {
for (Player player : game.getPlayers()) {
if (duePlayers.containsKey(player.getName())) {
gamesToBeClosed.add(game);
}
}
}
// Close the games.
for (Game game : gamesToBeClosed) {
GameManager.terminateGame(game, "One of the players did not answer.");
}
// Clear the list.
gamesToBeClosed.clear();
}
}
}